package com.imooc.util;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author Tim
 * @Description: 加解密类
 * @Version 1.0
 */
public class EncryptUtil {

    public static final String SIGN_TYPE_RSA = "RSA";

    /** RSA最大加密明文大小 */
    private static final int MAX_ENCRYPT_BLOCK = 245;
    /** RSA最大解密密文大小 */
    private static final int MAX_DECRYPT_BLOCK = 256;


    /**
     * @author Tim
     * @description: 生成公私钥
     */
    public static Map<String, String> generateRSAKeyPairs() throws NoSuchAlgorithmException {
        Map<String, String> map = new HashMap<>();
        KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
        generator.initialize(2048);
        KeyPair keyPair = generator.genKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        map.put("publicKey", Base64.encodeBase64String(publicKey.getEncoded()));
        map.put("privateKey", Base64.encodeBase64String(privateKey.getEncoded()));
        return map;
    }

    /**
     * 加密
     * @param content
     * @param publicKey
     * @param charset
     * @return
     */
    public static String rsaEncrypt(String content, String publicKey, String charset) {
        ByteArrayOutputStream out = null;
        try {
            PublicKey pubKey = getPublicKeyFromX509(SIGN_TYPE_RSA, new ByteArrayInputStream(publicKey.getBytes()));
            Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            byte[] data = StringUtils.isBlank(charset) ? content.getBytes() : content.getBytes(charset);
            int inputLen = data.length;
            out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = encodeBase64(out.toByteArray());

            return StringUtils.isBlank(charset) ? new String(encryptedData) : new String(encryptedData, charset);
        } catch (Exception e) {
            throw new RuntimeException("EncryptContent = " + content + ",charset = " + charset, e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("EncryptContent = " + content + ",charset = " + charset, e);
            }
        }
    }

    /**
     * 解密
     * @param content
     * @param privateKey
     * @param charset
     * @return
     */
    public static String rsaDecrypt(String content, String privateKey, String charset) {

        ByteArrayOutputStream out = null;
        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
            Cipher cipher = Cipher.getInstance(SIGN_TYPE_RSA);
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            byte[] encryptedData = StringUtils.isEmpty(charset) ? decodeBase64(content.getBytes())
                    : decodeBase64(content.getBytes(charset));
            int inputLen = encryptedData.length;
            out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();

            return StringUtils.isEmpty(charset) ? new String(decryptedData) : new String(decryptedData, charset);
        } catch (Exception e) {
            throw new RuntimeException("EncodeContent = " + content + ",charset = " + charset, e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("EncryptContent = " + content + ",charset = " + charset, e);
            }
        }
    }

    public static byte[] encodeBase64(final byte[] binaryData) {
        return Base64.encodeBase64(binaryData);
    }

    public static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {

        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        byte[] encodedKey = IOUtils.toByteArray(ins);

        encodedKey = decodeBase64(encodedKey);

        return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
    }

    public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {

        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        byte[] encodedKey = IOUtils.toByteArray(ins);

        encodedKey = decodeBase64(encodedKey);

        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
    }

    public static byte[] decodeBase64(final byte[] base64Data) {
        return Base64.decodeBase64(base64Data);
    }

    public static void main(String[] args) throws NoSuchAlgorithmException {
//        Map<String, String> map = generateRSAKeyPairs();
//        System.out.println("publicKey: "+map.get("publicKey"));
//        System.out.println("privateKey: "+map.get("privateKey"));
        String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAopXm9FPd8fCwXGPRo7LV/IM8QbzQpgJNg8npmywEJe2XQ3ao7ud/SeWYUZ2SNncKiteZXBHShcHCCisfZO3gq1X5LReWZJugJ8cYs5ybpMZYBN8iTrKbJP5lAzGLpzqjsmY4y/90mGrL2OAFVEnctEcFSryxaXO4GH7RYdv+Oa0TWPZUYNmI8j4FfXDb11h7ztvOgmlebW7Jngbgo9FvE+BPHfTQXE6HkNdVBD9rjYEU0pPtQgy6GLjvmihPmk3alx2y8Ysru6RAdY/zqrQRYvmxss5U1MTap0oDFmdM+bSYgABuAgpnCKMcGuEO7RC98DNlapjaRMiiY7ya4ZNixwIDAQAB";
        String cipherText = rsaEncrypt("imooc", publicKey, null);

        System.out.println("密文："+cipherText);
        String privateKey = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCileb0U93x8LBcY9GjstX8gzxBvNCmAk2DyembLAQl7ZdDdqju539J5ZhRnZI2dwqK15lcEdKFwcIKKx9k7eCrVfktF5Zkm6AnxxiznJukxlgE3yJOspsk/mUDMYunOqOyZjjL/3SYasvY4AVUSdy0RwVKvLFpc7gYftFh2/45rRNY9lRg2YjyPgV9cNvXWHvO286CaV5tbsmeBuCj0W8T4E8d9NBcToeQ11UEP2uNgRTSk+1CDLoYuO+aKE+aTdqXHbLxiyu7pEB1j/OqtBFi+bGyzlTUxNqnSgMWZ0z5tJiAAG4CCmcIoxwa4Q7tEL3wM2VqmNpEyKJjvJrhk2LHAgMBAAECgf8BKhGoA5xonyX5g2gnmUrvBAGVIZpc61H4H6S3TRllLCO+gpsIcEqjvNaFFHEiSA1LRpVSk3jD0coR8z0bpph1TFL8f66+tF6b6lEYTYbP0fEkwnRRxqBVzidAXpp2wsoT/PzAeT6HLjyc0P3jcflNUTTjKXbGdThcSeGLUd1VjGTTpwzBek54fd0a4NIqtiDxEaEG/eLwDRmL46+2lVKS3WNuFqAYTRgFI1vvCiRcVA47jMDpLvhGKq/g132/Btadjkc1vlec/UBOkBRrTqtczn/DvmEeUJ0UgylHoc6enP2aCyLuGfy4vpgKJ4Y5V+p/tLxTHvK+8Mku/v2wt1ECgYEAvYBq5JLjjOlwNq9Wt7swi+HWA0CKRfpNzPWHspCG0DUMym9K2qJnvA2KQ95HaGH2BZNdthL7EWvjcqsj/EWAcNh1Omf9eZbYRJa/p7JJBOZlurSJOs32w3n1GdZszQrlIDLb/NPQaTZMfmeoD2yQe0IMZcE4jh0SXYpeYrGRIHECgYEA26OFZVW+vWrNqglqTF1zCRdBvdPrIk1KTB5eq2H/zsSqRD2K82cSQZF1Nah5ERhoNUfDyE8iUYJ907xzpCPBm7YrsIaGaQ3Ho/jI2h919MW+j/OlW9Rn4AR5BkWRRYWRiNQDLwz/uWwqjUq7s2RstcupKev0XsLgYg8XTLYBUrcCgYB1YZ1U1pPzDgo/JV8O0fmhPBOH0iuUaxZUFeanOhT31VSF9Ggmg6DEAJMbtQ0AYGW3UHAkvx8bzHuH5PCWbCrCO6Jpuk5tSX3PBp3S74GBcoGjI/85GKl4rgufpI2ieNQynK5droqTEVpSljyoyTO51dC+GvDAPsvIe1Q5yAnAUQKBgQCRf+8DpVzZMGKULO6NLi7hq9VyQmv26kq4yDmgwSfsOt4snLRq8DHo28pQxI2HJKsL+MGa9clfQ9nFJYdaChHc7D5fD48FEwrY9htgXA0RHNpTlG1LuxchEPV3kShebNBvWzvMtIjHtf8uYH6dpX5apFMpIVVeXSy16RdfSwbvWwKBgQCusqgLd305TryYPgAcsFAIYGmOEqT3yJ3p4beCK5PA5Q/MzOP9vTqJ82U31lUQxYT6AzteHSLEor1WuS3QXcMLY/MM+eDWKk7MjLSICO+8UcM4lMmMy8kdy9vbtQ5ui4jejHmjb+uuykLtNdzcA6QNPXfRnU0/N4VfX4JjqWssHA==";
        String plainText = rsaDecrypt(cipherText, privateKey, null);
        System.out.println("解密之后："+plainText);
    }
}
